package gin

import (
	"bytes"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/pkg/errors"
	"io/ioutil"
	"strings"
)

// 获取 GIN 请求参数结构体
type RequestParam struct {
	UriKeys           []*ArgsKey        `json:"uri_keys,omitempty"`     // 指定接收 URI 参数信息，由 UriAccept 接收值
	UriObj            interface{}       `json:"uri_obj,omitempty"`      // 指定接收 URI 参数的对象，对象指针自己接收值
	QueryStringKeys   []*ArgsKey        `json:"query_keys,omitempty"`   // 指定接收 QueryString 参数信息，由 QueryStringAccept 接收值
	QueryStringObj    interface{}       `json:"query_Obj,omitempty"`    // 指定接收 QueryString 参数的对象，对象指针自己接收值
	Body              interface{}       `json:"body,omitempty"`         // 指定接收 Body 参数的对象，对象指针自己接收值，Body 完整字符串由 BodyAccept 接收
	BodyNoJson        bool              `json:"body_no_json,omitempty"` // 指定接收 Body 参数是否需要使用 BindJson，true 则不使用
	UriAccept         map[string]string `json:"uri_accept,omitempty"`
	QueryStringAccept map[string]string `json:"query_accept,omitempty"`
	BodyAccept        string            `json:"body_accept,omitempty"`
	SplitQueryString  bool              `json:"split_query_string,omitempty"` // 是否获取 URI，默认不获取
	QueryString       string            `json:"query_string,omitempty"`
	Uri               string            `json:"uri,omitempty"`
}

// 单个参数接收信息
type ArgsKey struct {
	Key          string   `json:"key,omitempty"`
	DefaultValue string   `json:"default_value,omitempty"`
	IsMust       bool     `json:"is_must,omitempty"`
	Validate     Validate `json:"validate"`
}

// 参数校验接口
type Validate interface {
	Validate(value string) bool
}

func (r *RequestParam) GetRequestArgs(c *gin.Context) error {
	if err := r.GetUriArgs(c); err != nil {
		return err
	}
	if err := r.GetQueryStringArgs(c); err != nil {
		return err
	}
	if err := r.GetBodyArgs(c); err != nil {
		return err
	}
	return nil
}

// 接收 URI 路由参数
func (r *RequestParam) GetUriArgs(c *gin.Context) error {
	// 1. 判断是否存在 URI 参数接收对象，存在则直接绑定赋值
	if r.UriObj != nil {
		return c.ShouldBindUri(r.UriObj)
	}
	// 2. 判断是否存在 URI 参数接收值，不存在则直接返回
	if r.UriKeys == nil {
		return nil
	}
	// 3. 获取 URI 参数值
	param := make(map[string]string)
	for _, v := range r.UriKeys {
		val := c.Param(v.Key)
		if val == "" && v.IsMust {
			return errors.New(fmt.Sprintf("%s cannot be empty", v.Key))
		}
		if val == "" && v.DefaultValue != "" {
			param[v.Key] = v.DefaultValue
		} else {
			param[v.Key] = val
		}
	}
	r.UriAccept = param
	return nil
}

// 接收 QueryString 参数
func (r *RequestParam) GetQueryStringArgs(c *gin.Context) error {
	// 1. 过滤请求参数包含 + 号特殊字符的问题
	if c.Request.URL != nil {
		c.Request.URL.RawQuery = strings.ReplaceAll(c.Request.URL.RawQuery, "+", "%2b")
	}
	// 2. 判断是否获取 URI
	if r.SplitQueryString {
		arr := strings.Split(c.Request.RequestURI, "?")
		r.Uri = arr[0]
		if len(arr) == 2 {
			r.QueryString = arr[1]
		}
	}
	// 3. 判断请求 query 参数是否为空
	if c.Request.URL.RawQuery == "" {
		return nil
	}
	// 4. 判断是否存在绑定对象，存在则直接绑定参数返回
	if r.QueryStringObj != nil {
		return c.ShouldBindQuery(r.QueryStringObj)
	}
	// 5. 判断是否存在要获取的 QueryString 参数值
	if r.QueryStringKeys == nil {
		return nil
	}
	// 6. 获取指定 QueryString 参数值
	param := make(map[string]string)
	for _, v := range r.QueryStringKeys {
		var val string
		if v.DefaultValue != "" {
			param[v.Key] = c.DefaultQuery(v.Key, v.DefaultValue)
			continue
		}
		val, ok := c.GetQuery(v.Key)
		if v.IsMust && !ok {
			return errors.New(fmt.Sprintf("%s cannot be empty", v.Key))
		}
		param[v.Key] = val
	}
	r.QueryStringAccept = param
	return nil
}

// 接收 Body 参数（GET-form/data、raw / POST-form/data、application/x-www-form-urlencoded、raw）
// 注意：GET-application/x-www-form-urlencode 这种请求是获取不到值的
func (r *RequestParam) GetBodyArgs(c *gin.Context) error {
	// 1. 获取 Body 参数
	body, err := ioutil.ReadAll(c.Request.Body)
	if err != nil || len(body) == 0 {
		return errors.WithStack(err)
	}
	// 2. 获取 body 的字符串内容，并将 Body 值重新写入 buffer
	r.BodyAccept = string(body)
	if r.Body == nil {
		return nil
	}
	// 3. 获取 Body 值
	c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(body))
	if !r.BodyNoJson && strings.Contains(r.BodyAccept, "{") {
		err = c.ShouldBindJSON(r.Body) // raw 传参
	} else {
		err = c.ShouldBind(r.Body) // form 传参
	}
	return errors.WithStack(err)
}
